/**
  ******************************************************************************
  * @file    bsp_i2c.c
  * @author  Iron
  * @date    2018-08-29
  * @version v1.0
  * @brief   bsp_i2c c file
  */

/** @addtogroup GROUP_BSP
  * @{
  */

/* includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "semphr.h"
#include "event_groups.h"
#include "stm32f4xx_hal.h"
#include "bsp.h"
#include "bsp_i2c.h"

// DEBUG
#include "debug_log.h"
#define LOG_LOCAL_LEVEL DBG_LOG_DEBUG
DBG_LOG_TAG("BSP_I2C");

/* private typedef -----------------------------------------------------------*/
/* private macro -------------------------------------------------------------*/
#define BSP_I2C_ENTER_ISR() \
  UBaseType_t uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR()

#define BSP_I2C_EXIT_ISR() \
  portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus)

#define bsp_i2c_rtos_delay(ms)  vTaskDelay( pdMS_TO_TICKS( ms ) )

#define BSP_I2C1_IRQ_PRE_PRI  8     /* 5 < PRI < 15*/
#define BSP_I2C1_IRQ_SUB_PRI      0

/* private variables ---------------------------------------------------------*/

/**
  * @breaf i2c1 rtos event
  */
#define BSP_I2C1_ERR_EVENT         ( 1 << 0 )
#define BSP_I2C1_MASTER_TXC_EVENT  ( 1 << 1 )
#define BSP_I2C1_MASTER_RXC_EVENT  ( 1 << 2 )
#define BSP_I2C1_MEM_TXC_EVENT     ( 1 << 3 )
#define BSP_I2C1_MEM_RXC_EVENT     ( 1 << 4 )

#define BSP_I2C1_OS_EVENT_INIT() \
    do{i2c1_event_handle = xEventGroupCreateStatic( &i2c1_event );}while(0)

#define BSP_I2C1_OS_EVENT_SET_FROM_ISR(events) \
    do{                                                                                                                                                                                                \
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;                                                     \
    if (xEventGroupSetBitsFromISR( i2c1_event_handle, events, &xHigherPriorityTaskWoken ) != pdFAIL) { \
      portYIELD_FROM_ISR( xHigherPriorityTaskWoken );                                                  \
    }                                                                                                                                                                                            \
    }while(0)

#define BSP_I2C1_OS_EVENT_CLEAR(events) \
    do{xEventGroupClearBits(i2c1_event_handle, events);}while(0)

#define BSP_I2C1_OS_EVENT_WAIT(events, is_clear, wait_all, timeout) \
    xEventGroupWaitBits( i2c1_event_handle, events, is_clear, wait_all, pdMS_TO_TICKS(timeout));

static EventGroupHandle_t i2c1_event_handle;
static StaticEventGroup_t i2c1_event;

/**
  * @breaf i2c1 mutex handle
  */
static SemaphoreHandle_t bsp_i2c1_mutex_handle;
static StaticSemaphore_t bsp_i2c1_mutex;

#define BSP_I2C1_OS_MUTEX_INIT() \
    do{bsp_i2c1_mutex_handle = xSemaphoreCreateMutexStatic(&bsp_i2c1_mutex);}while(0)

#define BSP_I2C1_OS_MUTEX_WAIT(timeout) \
    do{                                                                                                                                                              \
        if( xSemaphoreTake(bsp_i2c1_mutex_handle, pdMS_TO_TICKS(timeout)) != pdPASS) {   \
            DBG_LOGE(TAG, "i2c1 mutex wait error or timeout.");                            \
            return BSP_I2C_ERR;                                                                                                                      \
        }                                                                                                                                                                \
    }while(0)

#define BSP_I2C1_OS_MUTEX_RELEASE() \
    do{xSemaphoreGive(bsp_i2c1_mutex_handle);}while(0)

/**
* @breaf hi2c1 handle
*/
I2C_HandleTypeDef hi2c1;

/* private function prototypes -----------------------------------------------*/
/* private functions ---------------------------------------------------------*/
extern void _Error_Handler(char *, int);

/**
* @brief This function handles I2C1 event interrupt.
*/
void I2C1_EV_IRQHandler(void)
{
    BSP_I2C_ENTER_ISR();
    HAL_I2C_EV_IRQHandler(&hi2c1);
    BSP_I2C_EXIT_ISR();
}

/**
* @brief This function handles I2C1 error interrupt.
*/
void I2C1_ER_IRQHandler(void)
{
    BSP_I2C_ENTER_ISR();
    HAL_I2C_ER_IRQHandler(&hi2c1);
    BSP_I2C_EXIT_ISR();
}

void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    if (hi2c->Instance == I2C1)
    {
        __HAL_RCC_GPIOB_CLK_ENABLE();

        /**I2C1 GPIO Configuration
        PB6     ------> I2C1_SCL
        PB7     ------> I2C1_SDA
        */
        GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

        /* Peripheral clock enable */
        __HAL_RCC_I2C1_CLK_ENABLE();

        /* I2C1 interrupt Init */
        HAL_NVIC_SetPriority(I2C1_EV_IRQn, BSP_I2C1_IRQ_PRE_PRI, BSP_I2C1_IRQ_SUB_PRI);
        HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);
        HAL_NVIC_SetPriority(I2C1_ER_IRQn, BSP_I2C1_IRQ_PRE_PRI, BSP_I2C1_IRQ_SUB_PRI);
        HAL_NVIC_EnableIRQ(I2C1_ER_IRQn);
    }
}

void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c)
{
    if (hi2c->Instance == I2C1)
    {
        /* Peripheral clock disable */
        __HAL_RCC_I2C1_CLK_DISABLE();

        /**I2C1 GPIO Configuration
        PB6     ------> I2C1_SCL
        PB7     ------> I2C1_SDA
        */
        HAL_GPIO_DeInit(GPIOB, GPIO_PIN_6 | GPIO_PIN_7);

        /* I2C1 interrupt DeInit */
        HAL_NVIC_DisableIRQ(I2C1_EV_IRQn);
        HAL_NVIC_DisableIRQ(I2C1_ER_IRQn);
    }
}

/* I2C1 init function */
static void MX_I2C1_Init(void)
{
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 400000;
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.OwnAddress2 = 0;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    if (HAL_I2C_Init(&hi2c1) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }
}

void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    if (hi2c->Instance == I2C1)
    {
        BSP_I2C1_OS_EVENT_SET_FROM_ISR(BSP_I2C1_MASTER_TXC_EVENT);
    }
}

void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    if (hi2c->Instance == I2C1)
    {
        BSP_I2C1_OS_EVENT_SET_FROM_ISR(BSP_I2C1_MASTER_RXC_EVENT);
    }
}

void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    if (hi2c->Instance == I2C1)
    {
        BSP_I2C1_OS_EVENT_SET_FROM_ISR(BSP_I2C1_MEM_TXC_EVENT);
    }
}

void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    if (hi2c->Instance == I2C1)
    {
        BSP_I2C1_OS_EVENT_SET_FROM_ISR(BSP_I2C1_MEM_RXC_EVENT);
    }
}

void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)
{
    if (hi2c->Instance == I2C1)
    {
        BSP_I2C1_OS_EVENT_SET_FROM_ISR(BSP_I2C1_ERR_EVENT);
    }
}

void bsp_i2c_clear_events(uint32_t events)
{
    BSP_I2C1_OS_EVENT_CLEAR(events | BSP_I2C1_ERR_EVENT);
}

int32_t bsp_i2c_wait_event(uint32_t events, uint32_t timeout)
{
    EventBits_t bits;

    bits = BSP_I2C1_OS_EVENT_WAIT(events, pdTRUE, pdFALSE, timeout);

    if (bits & BSP_I2C1_ERR_EVENT)
    {
        DBG_LOGE(TAG, "I2C1 get error event.(state=%d [0x%02X], ErrorCode=%d [0x%02X])",
                 hi2c1.State, hi2c1.State, hi2c1.ErrorCode, hi2c1.ErrorCode);

        HAL_I2C_DeInit(&hi2c1);
        MX_I2C1_Init();

        return BSP_I2C_ERR;
    }
    else
    {
        return (bits & events) ? BSP_I2C_NO_ERR : BSP_I2C_TIMEOUT;
    }
}

int32_t bsp_i2c_init(void)
{
    BSP_I2C1_OS_EVENT_INIT();
    BSP_I2C1_OS_MUTEX_INIT();

    MX_I2C1_Init();

    return BSP_I2C_NO_ERR;
}

int32_t bsp_i2c_mem_device_is_ready(uint8_t dev_addr, uint32_t timeout)
{
    uint32_t tms;

    tms = bsp_get_time_ms();

    if (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
    {
        DBG_LOGE(TAG, "I2C1 memory device not ready.(state=%d [0x%02X], ErrorCode=%d [0x%02X])",
                 hi2c1.State, hi2c1.State, hi2c1.ErrorCode, hi2c1.ErrorCode);
        return BSP_I2C_FLASE;
    }

    // This function is used with Memory devices, eeprom 24lc02 page (8bytes) write usually 4ms
    while (HAL_I2C_IsDeviceReady(&hi2c1, dev_addr, 1, timeout) != HAL_OK)
    {
        if ((bsp_get_time_ms() - tms) > timeout)
        {
            DBG_LOGE(TAG, "I2C1 mem device addr:0x%02X(%d) wait ready timeout.", dev_addr, dev_addr);
            return BSP_I2C_FLASE;
        }
        bsp_i2c_rtos_delay(1); // 1ms
    }

    return BSP_I2C_TRUE;
}

int32_t bsp_i2c_write(uint8_t dev_addr, uint8_t *buf, uint16_t len, uint32_t timeout)
{
    int32_t res = BSP_I2C_NO_ERR;

    BSP_I2C1_OS_MUTEX_WAIT(timeout);

    if (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
    {
        DBG_LOGE(TAG, "bsp_i2c_write: I2C1 not ready.(state=%d [0x%02X], ErrorCode=%d [0x%02X])",
                 hi2c1.State, hi2c1.State, hi2c1.ErrorCode, hi2c1.ErrorCode);
        res = BSP_I2C_ERR;
        goto __exit;
    }

    bsp_i2c_clear_events(BSP_I2C1_MASTER_TXC_EVENT);

    if (HAL_I2C_Master_Transmit_IT(&hi2c1, dev_addr, buf, len) != HAL_OK)
    {
        res = BSP_I2C_ERR;
        DBG_LOGE(TAG, "bsp_i2c_write: IT mode transmit error.");
        goto __exit;
    }

    if (bsp_i2c_wait_event(BSP_I2C1_MASTER_TXC_EVENT, timeout) != BSP_I2C_NO_ERR)
    {
        DBG_LOGE(TAG, "bsp_i2c_write: wait txc event timeout.");
        res = BSP_I2C_TIMEOUT;
    }

__exit:
    BSP_I2C1_OS_MUTEX_RELEASE();
    return res;
}

int32_t bsp_i2c_read(uint8_t dev_addr, uint8_t *data, uint16_t len, uint32_t timeout)
{
    int32_t res = BSP_I2C_NO_ERR;

    BSP_I2C1_OS_MUTEX_WAIT(timeout);

    if (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
    {
        DBG_LOGE(TAG, "bsp_i2c_read: I2C1 not ready.(state=%d [0x%02X], ErrorCode=%d [0x%02X])",
                 hi2c1.State, hi2c1.State, hi2c1.ErrorCode, hi2c1.ErrorCode);
        res = BSP_I2C_ERR;
        goto __exit;
    }

    bsp_i2c_clear_events(BSP_I2C1_MASTER_RXC_EVENT);

    if (HAL_I2C_Master_Receive_IT(&hi2c1, dev_addr, data, len) != HAL_OK)
    {
        DBG_LOGE(TAG, "bsp_i2c_read: IT mode receive error.");
        res = BSP_I2C_ERR;
        goto __exit;
    }

    if (bsp_i2c_wait_event(BSP_I2C1_MASTER_RXC_EVENT, timeout) != BSP_I2C_NO_ERR)
    {
        DBG_LOGE(TAG, "bsp_i2c_read: wait rxc event timeout.");
        res = BSP_I2C_TIMEOUT;
    }

__exit:
    BSP_I2C1_OS_MUTEX_RELEASE();
    return res;
}

int32_t bsp_i2c_mem_page_write(uint8_t dev_addr, uint16_t mem_addr, uint16_t page_size, uint16_t mem_addr_bits, uint8_t *data, uint16_t len, uint32_t timeout)
{
    int32_t res = BSP_I2C_NO_ERR;
    uint16_t wlen, n, MemAddSize;

    BSP_I2C1_OS_MUTEX_WAIT(timeout);

    MemAddSize = (mem_addr_bits == BSP_I2C_MEMADD_16BIT) ? I2C_MEMADD_SIZE_16BIT : I2C_MEMADD_SIZE_8BIT;

    for (wlen = 0; wlen < len; wlen += n)
    {
        if (bsp_i2c_mem_device_is_ready(dev_addr, timeout) != BSP_I2C_TRUE)
        {
            res = BSP_I2C_ERR;
            break;
        }
        n = (len - wlen);
        if (n > page_size) n = page_size;

        bsp_i2c_clear_events(BSP_I2C1_MEM_TXC_EVENT);

        if (HAL_I2C_Mem_Write_IT(&hi2c1, dev_addr, mem_addr + wlen, MemAddSize, &data[wlen], n) != HAL_OK)
        {
            res = BSP_I2C_ERR;
            DBG_LOGE(TAG, "bsp_i2c_mem_page_write: IT mode mem write error.");
            break;
        }

        if (bsp_i2c_wait_event(BSP_I2C1_MEM_TXC_EVENT, timeout) != BSP_I2C_NO_ERR)
        {
            res = BSP_I2C_TIMEOUT;
            DBG_LOGE(TAG, "bsp_i2c_mem_page_write: wait txc event timeout.");
            break;
        }
    }

    BSP_I2C1_OS_MUTEX_RELEASE();
    return res;
}


int32_t bsp_i2c_mem_write(uint8_t dev_addr, uint16_t mem_addr, uint16_t mem_addr_bits, uint8_t *data, uint16_t len, uint32_t timeout)
{
    int32_t res = BSP_I2C_NO_ERR;
    uint16_t MemAddSize;

    BSP_I2C1_OS_MUTEX_WAIT(timeout);

    MemAddSize = (mem_addr_bits == BSP_I2C_MEMADD_16BIT) ? I2C_MEMADD_SIZE_16BIT : I2C_MEMADD_SIZE_8BIT;

    if (bsp_i2c_mem_device_is_ready(dev_addr, timeout) != BSP_I2C_TRUE)
    {
        res = BSP_I2C_ERR;
        goto __exit;
    }

    bsp_i2c_clear_events(BSP_I2C1_MEM_TXC_EVENT);

    if (HAL_I2C_Mem_Write_IT(&hi2c1, dev_addr, mem_addr, MemAddSize, data, len) != HAL_OK)
    {
        res = BSP_I2C_ERR;
        DBG_LOGE(TAG, "bsp_i2c_mem_write: IT mode mem write error.");
        goto __exit;
    }

    if (bsp_i2c_wait_event(BSP_I2C1_MEM_TXC_EVENT, timeout) != BSP_I2C_NO_ERR)
    {
        res = BSP_I2C_TIMEOUT;
        DBG_LOGE(TAG, "bsp_i2c_mem_write: wait txc event timeout.");
    }

__exit:
    BSP_I2C1_OS_MUTEX_RELEASE();
    return res;
}

int32_t bsp_i2c_mem_read(uint8_t dev_addr, uint16_t mem_addr, uint16_t mem_addr_bits, uint8_t *data, uint16_t len, uint32_t timeout)
{
    int32_t res = BSP_I2C_NO_ERR;
    uint16_t MemAddSize;

    BSP_I2C1_OS_MUTEX_WAIT(timeout);

    MemAddSize = (mem_addr_bits == BSP_I2C_MEMADD_16BIT) ? I2C_MEMADD_SIZE_16BIT : I2C_MEMADD_SIZE_8BIT;

    if (bsp_i2c_mem_device_is_ready(dev_addr, timeout) != BSP_I2C_TRUE)
    {
        res = BSP_I2C_ERR;
        goto __exit;
    }

    bsp_i2c_clear_events(BSP_I2C1_MEM_RXC_EVENT);

    if (HAL_I2C_Mem_Read_IT(&hi2c1, dev_addr, mem_addr, MemAddSize, data, len) != HAL_OK)
    {
        DBG_LOGE(TAG, "bsp_i2c_mem_read: IT mode mem write error.");
        res = BSP_I2C_ERR;
        goto __exit;
    }

    if (bsp_i2c_wait_event(BSP_I2C1_MEM_RXC_EVENT, timeout) != BSP_I2C_NO_ERR)
    {
        DBG_LOGE(TAG, "bsp_i2c_mem_read: wait rxc timeout.");
        res = BSP_I2C_TIMEOUT;
        goto __exit;
    }

__exit:
    BSP_I2C1_OS_MUTEX_RELEASE();
    return res;
}

/**
  * @}
  */

/******************* (C)COPYRIGHT 2018 ***** END OF FILE *********************/
